package com.chan.mediacamera.clip;

import android.media.MediaCodec;
import android.media.MediaCodecInfo;
import android.media.MediaExtractor;
import android.media.MediaFormat;
import android.os.Build;
import android.support.annotation.RequiresApi;
import android.util.Log;

import com.chan.mediacamera.camera.media.MuxerWapper;

import java.io.IOException;
import java.nio.ByteBuffer;

import static android.media.MediaExtractor.SEEK_TO_PREVIOUS_SYNC;

@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2)
public class VideoClipper1 implements Runnable {

    private final String TAG = "VideoClipper1";

    final int TIMEOUT_USEC = 0;
    private MediaExtractor mVideoExtractor;
    private MediaCodec mVideoEncoder;
    private MediaCodec mVideoDecoder;
    private InputSurface mInputSurface;
    private OutputSurface mOutputSurface;
    private long mStartPosition;
    private long mClipDur;

    protected MediaClipper.ClipperConfig mConfig;

    private OnClipperListener mlistener;
    private int muxVideoTrack = -1;
    private int mVideoTrackIndex;
    private MediaFormat mVideoFormat;
    private long mFirstVideoTime;

    public VideoClipper1(OnClipperListener listener) {
        mlistener = listener;
    }

    @Override
    public void run() {
        start();
        startVideoCodec(mVideoDecoder, mVideoEncoder, mVideoExtractor, mInputSurface, mOutputSurface, mFirstVideoTime, mStartPosition, mClipDur);
        end();
    }

    public void startClip(MediaClipper.ClipperConfig config, long startPosition, long dur) throws IOException {
        mConfig = config;
        mStartPosition = startPosition;
        mClipDur = dur;
        prepare();

        mVideoExtractor = new MediaExtractor();
        mVideoExtractor.setDataSource(mConfig.mPath);
        //音轨和视轨初始化
        for (int i = 0; i < mVideoExtractor.getTrackCount(); i++) {
            MediaFormat format = mVideoExtractor.getTrackFormat(i);
            if (format.getString(MediaFormat.KEY_MIME).startsWith("video/")) {
                mVideoTrackIndex = i;
                mVideoFormat = format;
                break;
            }
        }
    }

    public void release() {
        if (mVideoExtractor != null) {
            mVideoExtractor.release();
            mVideoExtractor = null;
        }
        if (mVideoEncoder != null) {
            mVideoEncoder.stop();
            mVideoEncoder.release();
            mVideoEncoder = null;
        }
        if (mVideoDecoder != null) {
            mVideoDecoder.stop();
            mVideoDecoder.release();
            mVideoDecoder = null;
        }
        if (mInputSurface != null) {
            mInputSurface.release();
            mInputSurface = null;
        }
        if (mOutputSurface != null) {
            mOutputSurface.release();
            mOutputSurface = null;
        }
    }

    protected void prepare() {
        try {
            mVideoDecoder = MediaCodec.createDecoderByType("video/avc");
            mVideoEncoder = MediaCodec.createEncoderByType("video/avc");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }


    protected void start() {
        mVideoExtractor.selectTrack(mVideoTrackIndex);
        mFirstVideoTime = mVideoExtractor.getSampleTime();
        mVideoExtractor.seekTo(mFirstVideoTime + mStartPosition, SEEK_TO_PREVIOUS_SYNC);

        // 巨大bug解决,只要decoder在encoder面前申明，截取的视频就会黑屏，换过来就没问题

        // Encoder
        MediaFormat mediaFormat = MediaFormat.createVideoFormat("video/avc", mConfig.mOutputWidth, mConfig.mOutputHeight);
        int mBitRate = (int) (mConfig.mVideoWidth * mConfig.mVideoHeight * 25 * 0.25 / 6);
        mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, mBitRate);
        mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 25);
        mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
        mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 5);
        mVideoEncoder.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
        mInputSurface = new InputSurface(mVideoEncoder.createInputSurface());
        mInputSurface.makeCurrent();
        mVideoEncoder.start();

        // decoder
        mOutputSurface = new OutputSurface(mConfig.mContext.getApplicationContext());
        mOutputSurface.setInputSize(mConfig.mOutputWidth, mConfig.mOutputHeight);
        mOutputSurface.setVideoSize(mConfig.mVideoWidth, mConfig.mVideoHeight);
        mVideoDecoder.configure(mVideoFormat, mOutputSurface.getSurface(), null, 0);
        mVideoDecoder.start();//解码器启动
    }

    protected void end() {
        if (mlistener != null) {
            mlistener.onStop(MuxerWapper.DATA_VIDEO);
        }
    }

    private void startVideoCodec(MediaCodec decoder, MediaCodec encoder, MediaExtractor extractor, InputSurface inputSurface, OutputSurface outputSurface, long firstSampleTime, long startPosition, long duration) {
        ByteBuffer[] decoderInputBuffers = decoder.getInputBuffers();
        ByteBuffer[] encoderOutputBuffers = encoder.getOutputBuffers();
        MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
        MediaCodec.BufferInfo outputInfo = new MediaCodec.BufferInfo();
        boolean done = false;//用于判断整个编解码过程是否结束
        boolean inputDone = false;
        boolean decodeDone = false;
        while (!done) {
            if (!inputDone) {
                int inputIndex = decoder.dequeueInputBuffer(TIMEOUT_USEC);
                if (inputIndex >= 0) {
                    ByteBuffer inputBuffer;
                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                        inputBuffer = decoder.getInputBuffer(inputIndex);
                    } else {
                        inputBuffer = decoderInputBuffers[inputIndex];
                    }
                    inputBuffer.clear();
                    int readSampleData = extractor.readSampleData(inputBuffer, 0);
                    long dur = extractor.getSampleTime() - firstSampleTime - startPosition;//当前已经截取的视频长度
                    if ((dur < duration) && readSampleData > 0) {
                        decoder.queueInputBuffer(inputIndex, 0, readSampleData, extractor.getSampleTime(), 0);
                        extractor.advance();
                    } else {
                        decoder.queueInputBuffer(inputIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
                        inputDone = true;
                    }
                }
            }
            if (!decodeDone) {
                int index = decoder.dequeueOutputBuffer(info, TIMEOUT_USEC);
                if (index == MediaCodec.INFO_TRY_AGAIN_LATER) {
                    // no output available yet
                } else if (index == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
                    //decoderOutputBuffers = decoder.getOutputBuffers();
                } else if (index == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
                    // expected before first buffer of data
                    //MediaFormat newFormat = decoder.getOutputFormat();
                } else if (index < 0) {
                } else {
                    boolean doRender = (info.size != 0 && info.presentationTimeUs - firstSampleTime > startPosition);
                    decoder.releaseOutputBuffer(index, doRender);
                    if (doRender) {
                        // This waits for the image and renders it after it arrives.
                        outputSurface.awaitNewImage();
                        outputSurface.onDrawFrame();
                        // Send it to the encoder.
                        inputSurface.setPresentationTime(info.presentationTimeUs * 1000);
                        inputSurface.swapBuffers();
                    }
                    if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
                        encoder.signalEndOfInputStream();
                        decodeDone = true;
                    }
                }
            }
            boolean encoderOutputAvailable = true;
            while (encoderOutputAvailable) {
                int encoderStatus = encoder.dequeueOutputBuffer(outputInfo, TIMEOUT_USEC);
                if (encoderStatus == MediaCodec.INFO_TRY_AGAIN_LATER) {
                    // no output available yet
                    encoderOutputAvailable = false;
                } else if (encoderStatus == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
                    encoderOutputBuffers = encoder.getOutputBuffers();
                } else if (encoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
                    MediaFormat newFormat = encoder.getOutputFormat();
                    muxVideoTrack = mlistener.onFormatChanged(newFormat);
                    mlistener.onStart(MuxerWapper.DATA_VIDEO);
                } else if (encoderStatus < 0) {
                } else {
                    ByteBuffer encodedData;
                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                        encodedData = encoder.getOutputBuffer(encoderStatus);
                    } else {
                        encodedData = encoderOutputBuffers[encoderStatus];
                    }
                    done = (outputInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0;
                    if (done) {
                        encoderOutputAvailable = false;
                    }
                    // Write the data to the output "file".
                    if (outputInfo.presentationTimeUs == 0 && !done) {
                        continue;
                    }
                    if (outputInfo.size != 0) {
                        encodedData.position(outputInfo.offset);
                        encodedData.limit(outputInfo.offset + outputInfo.size);
                        mlistener.onProcess(outputInfo.presentationTimeUs);
                        mlistener.writeData(muxVideoTrack, encodedData, outputInfo);
                    }

                    encoder.releaseOutputBuffer(encoderStatus, false);
                }
                if (encoderStatus != MediaCodec.INFO_TRY_AGAIN_LATER) {
                    // Continue attempts to drain output.
                    continue;
                }
            }
        }
    }


}
